use std::fs;
use std::path::PathBuf;

use genco::prelude::*;
use xshell::Shell;

use crate::cairo_spec::get_spec;
use crate::spec::{Member, Node, NodeKind, Variant, Variants};

pub fn project_root() -> PathBuf {
    // This is the directory of Cargo.toml of the syntax_codegen crate.
    let dir = env!("CARGO_MANIFEST_DIR");
    // Pop the "/crates/cairo-lang-syntax-codegen" suffix.
    let res = PathBuf::from(dir).parent().unwrap().parent().unwrap().to_owned();
    assert!(res.join("Cargo.toml").exists(), "Could not find project root directory.");
    res
}

pub fn ensure_file_content(filename: PathBuf, content: String) {
    if let Ok(old_contents) = fs::read_to_string(&filename)
        && old_contents == content
    {
        return;
    }

    fs::write(&filename, content).unwrap();
}

pub fn get_codes() -> Vec<(String, String)> {
    vec![
        (
            "crates/cairo-lang-syntax/src/node/ast.rs".into(),
            reformat_rust_code(generate_ast_code().to_string().unwrap()),
        ),
        (
            "crates/cairo-lang-syntax/src/node/kind.rs".into(),
            reformat_rust_code(generate_kinds_code().to_string().unwrap()),
        ),
        (
            "crates/cairo-lang-syntax/src/node/key_fields.rs".into(),
            reformat_rust_code(generate_key_fields_code().to_string().unwrap()),
        ),
    ]
}

pub fn reformat_rust_code(text: String) -> String {
    // Since rustfmt is used with nightly features, it takes 2 runs to reach a fixed point.
    reformat_rust_code_inner(reformat_rust_code_inner(text))
}
pub fn reformat_rust_code_inner(text: String) -> String {
    let sh = Shell::new().unwrap();
    let cmd = sh.cmd("rustfmt").env("RUSTUP_TOOLCHAIN", "nightly-2025-12-05");
    let cmd_with_args = cmd.arg("--config-path").arg(project_root().join("rustfmt.toml"));
    let mut stdout = cmd_with_args.stdin(text).read().unwrap();
    if !stdout.ends_with('\n') {
        stdout.push('\n');
    }
    stdout
}

fn generate_kinds_code() -> rust::Tokens {
    let spec = get_spec();
    let mut tokens = quote! {
        $("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.")
        use core::fmt;
        use serde::{Deserialize, Serialize};
    };

    // Definition of SyntaxKind.
    let kinds = name_tokens(&spec, |k| !matches!(k, NodeKind::Enum { .. }));
    let token_kinds = name_tokens(&spec, |k| matches!(k, NodeKind::Token { .. }));
    let keyword_token_kinds =
        name_tokens(&spec, |k| matches!(k, NodeKind::Token { is_keyword } if *is_keyword));
    let terminal_kinds = name_tokens(&spec, |k| matches!(k, NodeKind::Terminal { .. }));
    let keyword_terminal_kinds =
        name_tokens(&spec, |k| matches!(k, NodeKind::Terminal { is_keyword, .. } if *is_keyword));

    tokens.extend(quote! {
        #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, salsa::Update, cairo_lang_proc_macros::HeapSize)]
        pub enum SyntaxKind {
            $(for t in kinds => $t,)
        }
        impl SyntaxKind {
            pub fn is_token(&self) -> bool {
                matches!(
                    *self,
                    $(for t in token_kinds join ( | ) => SyntaxKind::$t)
                )
            }
            pub fn is_terminal(&self) -> bool {
                matches!(
                    *self,
                    $(for t in terminal_kinds join ( | ) => SyntaxKind::$t)
                )
            }
            pub fn is_keyword_token(&self) -> bool {
                matches!(
                    *self,
                    $(for t in keyword_token_kinds join ( | ) => SyntaxKind::$t)
                )
            }
            pub fn is_keyword_terminal(&self) -> bool {
                matches!(
                    *self,
                    $(for t in keyword_terminal_kinds join ( | ) => SyntaxKind::$t)
                )
            }
        }
        impl fmt::Display for SyntaxKind {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "{self:?}")
            }
        }
    });
    tokens
}

/// Returns an iterator to the names of the tokens matching `predicate`.
fn name_tokens(spec: &[Node], predicate: impl Fn(&NodeKind) -> bool) -> impl Iterator<Item = &str> {
    spec.iter().filter(move |n| predicate(&n.kind)).map(|n| n.name.as_str())
}

fn generate_key_fields_code() -> rust::Tokens {
    let spec = get_spec();
    let mut arms = rust::Tokens::new();

    for Node { name, kind } in spec {
        match kind {
            NodeKind::Struct { members } | NodeKind::Terminal { members, .. } => {
                let mut fields = rust::Tokens::new();
                let mut key_fields_range = 0..0;
                for (i, member) in members.into_iter().enumerate() {
                    let field_name = member.name;
                    if member.key {
                        if key_fields_range.is_empty() {
                            key_fields_range = i..(i + 1);
                        } else {
                            assert_eq!(key_fields_range.end, i, "Key fields must be contiguous.");
                            key_fields_range.end = i + 1;
                        }
                        if !fields.is_empty() {
                            fields.extend(quote! { $(", ") });
                        }
                        fields.extend(quote!($field_name));
                    }
                }
                if !fields.is_empty() {
                    arms.extend(quote! {
                        $("\n// Key fields:") $fields.$("\n")
                    });
                }
                let key_fields_range =
                    format!("{}..{}", key_fields_range.start, key_fields_range.end);
                arms.extend(quote! {
                    SyntaxKind::$name => $key_fields_range,
                });
            }
            NodeKind::List { .. } | NodeKind::SeparatedList { .. } | NodeKind::Token { .. } => {
                arms.extend(quote! {
                    SyntaxKind::$name => 0..0,
                });
            }
            NodeKind::Enum { .. } => {}
        }
    }
    let tokens = quote! {
        $("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.")
        use super::kind::SyntaxKind;
        $("/// Gets the vector of children ids that are the indexing key for this SyntaxKind.")
        $("///")
        $("/// Each SyntaxKind has some children that are defined in the spec to be its indexing key")
        $("/// for its stable pointer. See [super::stable_ptr].")
        pub fn key_fields_range(kind: SyntaxKind) -> core::ops::Range<usize> {
            match kind {
                $arms
            }
        }
    };
    tokens
}

fn generate_ast_code() -> rust::Tokens {
    let spec = get_spec();
    let mut tokens = quote! {
        $("// Autogenerated file. To regenerate, please run `cargo run --bin generate-syntax`.")
        #![allow(clippy::match_single_binding)]
        #![allow(clippy::too_many_arguments)]
        #![allow(dead_code)]
        #![allow(unused_variables)]
        use std::ops::Deref;

        use cairo_lang_filesystem::span::TextWidth;
        use cairo_lang_filesystem::ids::SmolStrId;
        use cairo_lang_utils::{extract_matches, Intern};
        use cairo_lang_proc_macros::HeapSize;

        use salsa::Database;

        use super::element_list::ElementList;
        use super::green::GreenNodeDetails;
        use super::kind::SyntaxKind;
        use super::{
            GreenId, GreenNode, SyntaxNode, SyntaxStablePtrId, Terminal, Token, TypedStablePtr,
            TypedSyntaxNode,
        };
        #[path = "ast_ext.rs"]
        mod ast_ext;
    };
    let spec_clone = spec.clone();
    let all_tokens: Vec<_> =
        spec_clone.iter().filter(|node| matches!(node.kind, NodeKind::Terminal { .. })).collect();
    for Node { name, kind } in spec {
        tokens.extend(match kind {
            NodeKind::Enum { variants, missing_variant } => {
                let variants_list = match variants {
                    Variants::List(variants) => variants,
                    Variants::AllTokens => all_tokens
                        .iter()
                        .map(|node| Variant { name: node.name.clone(), kind: node.name.clone() })
                        .collect(),
                };
                gen_enum_code(name, variants_list, missing_variant)
            }
            NodeKind::Struct { members } => gen_struct_code(name, members, false),
            NodeKind::Terminal { members, .. } => gen_struct_code(name, members, true),
            NodeKind::Token { .. } => gen_token_code(name),
            NodeKind::List { element_type } => gen_list_code(name, element_type),
            NodeKind::SeparatedList { element_type, separator_type } => {
                gen_separated_list_code(name, element_type, separator_type)
            }
        });
    }
    tokens
}

fn gen_list_code(name: String, element_type: String) -> rust::Tokens {
    // TODO(spapini): Change Deref to Borrow.
    let ptr_name = format!("{name}Ptr");
    let green_name = format!("{name}Green");
    let element_green_name = format!("{element_type}Green");
    let common_code = gen_common_list_code(&name, &green_name, &ptr_name);
    quote! {
        #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
        pub struct $(&name)<'db>(ElementList<'db, $(&element_type)<'db>, 1>);
        impl<'db> Deref for $(&name)<'db>{
            type Target = ElementList<'db, $(&element_type)<'db>, 1>;
            fn deref(&self) -> &Self::Target {
                &self.0
            }
        }
        impl<'db> $(&name)<'db>{
            pub fn new_green(
                db: &'db dyn Database, children: &[$(&element_green_name)<'db>]
            ) -> $(&green_name)<'db> {
                let width = children.iter().map(|id|
                    id.0.long(db).width(db)).sum();
                $(&green_name)(GreenNode {
                    kind: SyntaxKind::$(&name),
                    details: GreenNodeDetails::Node {
                        children: children.iter().map(|x| x.0).collect(),
                        width,
                    },
                }.intern(db))
            }
        }
        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
        pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
        impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
            type SyntaxNode = $(&name)<'db>;
            fn untyped(self) -> SyntaxStablePtrId<'db> {
                self.0
            }
            fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
                $(&name)::from_syntax_node(db, self.0.lookup(db))
            }
        }
        impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
            fn from(ptr: $(&ptr_name)<'db>) -> Self {
                ptr.untyped()
            }
        }
        $common_code
    }
}

fn gen_separated_list_code(
    name: String,
    element_type: String,
    separator_type: String,
) -> rust::Tokens {
    // TODO(spapini): Change Deref to Borrow.
    let ptr_name = format!("{name}Ptr");
    let green_name = format!("{name}Green");
    let element_or_separator_green_name = format!("{name}ElementOrSeparatorGreen");
    let element_green_name = format!("{element_type}Green");
    let separator_green_name = format!("{separator_type}Green");
    let common_code = gen_common_list_code(&name, &green_name, &ptr_name);
    quote! {
        #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
        pub struct $(&name)<'db>(ElementList<'db, $(&element_type)<'db>, 2>);
        impl<'db> Deref for $(&name)<'db>{
            type Target = ElementList<'db, $(&element_type)<'db>, 2>;
            fn deref(&self) -> &Self::Target {
                &self.0
            }
        }
        impl<'db> $(&name)<'db>{
            pub fn new_green(
                db: &'db dyn Database, children: &[$(&element_or_separator_green_name)<'db>]
            ) -> $(&green_name)<'db> {
                let width = children.iter().map(|id|
                    id.id().long(db).width(db)).sum();
                $(&green_name)(GreenNode {
                    kind: SyntaxKind::$(&name),
                    details: GreenNodeDetails::Node {
                        children: children.iter().map(|x| x.id()).collect(),
                        width,
                    },
                }.intern(db))
            }
        }
        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
        pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
        impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
            type SyntaxNode = $(&name)<'db>;
            fn untyped(self) -> SyntaxStablePtrId<'db> {
                self.0
            }
            fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
                $(&name)::from_syntax_node(db, self.0.lookup(db))
            }
        }
        impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
            fn from(ptr: $(&ptr_name)<'db>) -> Self {
                ptr.untyped()
            }
        }
        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
        pub enum $(&element_or_separator_green_name)<'db> {
            Separator($(&separator_green_name)<'db>),
            Element($(&element_green_name)<'db>),
        }
        impl<'db> From<$(&separator_green_name)<'db>> for $(&element_or_separator_green_name)<'db> {
            fn from(value: $(&separator_green_name)<'db>) -> Self {
                $(&element_or_separator_green_name)::Separator(value)
            }
        }
        impl<'db> From<$(&element_green_name)<'db>> for $(&element_or_separator_green_name)<'db> {
            fn from(value: $(&element_green_name)<'db>) -> Self {
                $(&element_or_separator_green_name)::Element(value)
            }
        }
        impl<'db> $(&element_or_separator_green_name)<'db> {
            fn id(&self) -> GreenId<'db> {
                match self {
                    $(&element_or_separator_green_name)::Separator(green) => green.0,
                    $(&element_or_separator_green_name)::Element(green) => green.0,
                }
            }
        }
        $common_code
    }
}

fn gen_common_list_code(name: &str, green_name: &str, ptr_name: &str) -> rust::Tokens {
    quote! {
        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
        pub struct $green_name<'db>(pub GreenId<'db>);
        impl<'db> TypedSyntaxNode<'db> for $name<'db> {
            const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$name);
            type StablePtr = $ptr_name<'db>;
            type Green = $green_name<'db>;
            fn missing(db: &'db dyn Database) -> Self::Green {
                $green_name(
                    GreenNode {
                        kind: SyntaxKind::$name,
                        details: GreenNodeDetails::Node { children: [].into(), width: TextWidth::default() },
                    }.intern(db)
                )
            }
            fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
                Self(ElementList::new(node))
            }
            fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
                if node.kind(db) == SyntaxKind::$name {
                    Some(Self(ElementList::new(node)))
                } else {
                    None
                }
            }
            fn as_syntax_node(&self) -> SyntaxNode<'db> {
                self.node
            }
            fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
                $ptr_name(self.node.stable_ptr(db))
            }
        }
    }
}

fn gen_enum_code(
    name: String,
    variants: Vec<Variant>,
    missing_variant: Option<Variant>,
) -> rust::Tokens {
    let ptr_name = format!("{name}Ptr");
    let green_name = format!("{name}Green");
    let mut enum_body = quote! {};
    let mut from_node_body = quote! {};
    let mut cast_body = quote! {};
    let mut ptr_conversions = quote! {};
    let mut green_conversions = quote! {};
    for variant in &variants {
        let n = &variant.name;
        let k = &variant.kind;

        enum_body.extend(quote! {
            $n($k<'db>),
        });
        from_node_body.extend(quote! {
            SyntaxKind::$k => $(&name)::$n($k::from_syntax_node(db, node)),
        });
        cast_body.extend(quote! {
            SyntaxKind::$k => Some($(&name)::$n($k::from_syntax_node(db, node))),
        });
        let variant_ptr = format!("{k}Ptr");
        ptr_conversions.extend(quote! {
            impl<'db> From<$(&variant_ptr)<'db>> for $(&ptr_name)<'db> {
                fn from(value: $(&variant_ptr)<'db>) -> Self {
                    Self(value.0)
                }
            }
        });
        let variant_green = format!("{k}Green");
        green_conversions.extend(quote! {
            impl<'db> From<$(&variant_green)<'db>> for $(&green_name)<'db> {
                fn from(value: $(&variant_green)<'db>) -> Self {
                    Self(value.0)
                }
            }
        });
    }
    let missing_body = match missing_variant {
        Some(missing) => quote! {
            $(&green_name)($(missing.kind)::missing(db).0)
        },
        None => quote! {
            panic!("No missing variant.");
        },
    };
    quote! {
        #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
        pub enum $(&name)<'db>{
            $enum_body
        }
        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
        pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
        impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
            type SyntaxNode = $(&name)<'db>;
            fn untyped(self) -> SyntaxStablePtrId<'db> {
                self.0
            }
            fn lookup(&self, db: &'db dyn Database) -> Self::SyntaxNode {
                $(&name)::from_syntax_node(db, self.0.lookup(db))
            }
        }
        impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
            fn from(ptr: $(&ptr_name)<'db>) -> Self {
                ptr.untyped()
            }
        }
        $ptr_conversions
        $green_conversions
        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
        pub struct $(&green_name)<'db>(pub GreenId<'db>);
        impl<'db> TypedSyntaxNode<'db> for $(&name)<'db>{
            const OPTIONAL_KIND: Option<SyntaxKind> = None;
            type StablePtr = $(&ptr_name)<'db>;
            type Green = $(&green_name)<'db>;
            fn missing(db: &'db dyn Database) -> Self::Green {
                $missing_body
            }
            fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
                let kind = node.kind(db);
                match kind{
                    $from_node_body
                    _ => panic!(
                        "Unexpected syntax kind {:?} when constructing {}.",
                        kind,
                        $[str]($[const](&name))),
                }
            }
            fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
                let kind = node.kind(db);
                match kind {
                    $cast_body
                    _ => None,
                }
            }
            fn as_syntax_node(&self) -> SyntaxNode<'db> {
                match self {
                    $(for v in &variants => $(&name)::$(&v.name)(x) => x.as_syntax_node(),)
                }
            }
            fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
                $(&ptr_name)(self.as_syntax_node().stable_ptr(db))
            }
        }
        impl<'db> $(&name)<'db> {
            $("/// Checks if a kind of a variant of [")$(&name)$("].")
            pub fn is_variant(kind: SyntaxKind) -> bool {
                matches!(kind, $(for v in &variants join (|) => SyntaxKind::$(&v.kind)))
            }
        }
    }
}

fn gen_token_code(name: String) -> rust::Tokens {
    let green_name = format!("{name}Green");
    let ptr_name = format!("{name}Ptr");

    quote! {
        #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
        pub struct $(&name)<'db> {
            node: SyntaxNode<'db>,
        }
        impl<'db> Token<'db> for $(&name)<'db> {
            fn new_green(db: &'db dyn Database, text: SmolStrId<'db>) -> Self::Green {
                $(&green_name)(GreenNode {
                    kind: SyntaxKind::$(&name),
                    details: GreenNodeDetails::Token(text),
                }.intern(db))
            }
            fn text(&self, db: &'db dyn Database) -> SmolStrId<'db> {
                *extract_matches!(&self.node.green_node(db).details,
                    GreenNodeDetails::Token)
            }
        }
        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
        pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
        impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
            type SyntaxNode = $(&name)<'db>;
            fn untyped(self) -> SyntaxStablePtrId<'db> {
                self.0
            }
            fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
                $(&name)::from_syntax_node(db, self.0.lookup(db))
            }
        }
        impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
            fn from(ptr: $(&ptr_name)<'db>) -> Self {
                ptr.untyped()
            }
        }
        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
        pub struct $(&green_name)<'db>(pub GreenId<'db>);
        impl<'db> $(&green_name)<'db> {
            pub fn text(&self, db: &'db dyn Database) -> SmolStrId<'db> {
                *extract_matches!(&self.0.long(db).details, GreenNodeDetails::Token)
            }
        }
        impl<'db> TypedSyntaxNode<'db> for $(&name)<'db>{
            const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$(&name));
            type StablePtr = $(&ptr_name)<'db>;
            type Green = $(&green_name)<'db>;
            fn missing(db: &'db dyn Database) -> Self::Green {
                $(&green_name)(GreenNode {
                    kind: SyntaxKind::TokenMissing,
                    details: GreenNodeDetails::Token(SmolStrId::from(db, "")),
                }.intern(db))
            }
            fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
                match node.green_node(db).details {
                    GreenNodeDetails::Token(_) => Self { node },
                    GreenNodeDetails::Node { .. } => panic!(
                        "Expected a token {:?}, not an internal node",
                        SyntaxKind::$(&name)
                    ),
                }
            }
            fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
                match node.green_node(db).details {
                    GreenNodeDetails::Token(_) => Some(Self { node }),
                    GreenNodeDetails::Node { .. } => None,
                }
            }
            fn as_syntax_node(&self) -> SyntaxNode<'db> {
                self.node
            }
            fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
                $(&ptr_name)(self.node.stable_ptr(db))
            }
        }
    }
}

fn gen_struct_code(name: String, members: Vec<Member>, is_terminal: bool) -> rust::Tokens {
    let green_name = format!("{name}Green");
    let mut body = rust::Tokens::new();
    let mut field_indices = quote! {};
    let mut args = quote! {};
    let mut params = quote! {};
    let mut args_for_missing = quote! {};
    let mut ptr_getters = quote! {};
    let mut key_field_index: usize = 0;
    for (i, Member { name, kind, key }) in members.iter().enumerate() {
        let index_name = format!("INDEX_{}", name.to_uppercase());
        field_indices.extend(quote! {
            pub const $index_name : usize = $i;
        });
        let key_name_green = format!("{name}_green");
        args.extend(quote! {$name.0,});
        // TODO(spapini): Validate that children SyntaxKinds are as expected.

        let child_green = format!("{kind}Green");
        params.extend(quote! {$name: $(&child_green)<'db>,});
        body.extend(quote! {
            pub fn $name(&self, db: &'db dyn Database) -> $kind<'db> {
                $kind::from_syntax_node(db, self.node.get_children(db)[$i])
            }
        });
        args_for_missing.extend(quote! {$kind::missing(db).0,});

        if *key {
            ptr_getters.extend(quote! {
                pub fn $(&key_name_green)(self, db: &'db dyn Database) -> $(&child_green)<'db> {
                    $(&child_green)(self.0.0.key_fields(db)[$key_field_index])
                }
            });
            key_field_index += 1;
        }
    }
    let ptr_name = format!("{name}Ptr");
    let new_green_impl = if is_terminal {
        let token_name = name.replace("Terminal", "Token");
        quote! {
            impl<'db> Terminal<'db> for $(&name)<'db> {
                const KIND: SyntaxKind = SyntaxKind::$(&name);
                type TokenType = $(&token_name)<'db>;
                fn new_green(
                    db: &'db dyn Database,
                    leading_trivia: TriviaGreen<'db>,
                    token: <<$(&name)<'db> as Terminal<'db>>::TokenType as TypedSyntaxNode<'db>>::Green,
                    trailing_trivia: TriviaGreen<'db>
                ) -> Self::Green {
                    let children = [$args];
                    let width =
                        children.into_iter().map(|id: GreenId<'_>| id.long(db).width(db)).sum();
                    $(&green_name)(GreenNode {
                        kind: SyntaxKind::$(&name),
                        details: GreenNodeDetails::Node { children: children.into(), width },
                    }.intern(db))
                }
                fn text(&self, db: &'db dyn Database) -> SmolStrId<'db> {
                    let GreenNodeDetails::Node{children,..} = &self.node.green_node(db).details else {
                        unreachable!("Expected a node, not a token");
                    };
                    *extract_matches!(&children[1].long(db).details, GreenNodeDetails::Token)
                }
            }
        }
    } else {
        quote! {
            impl<'db> $(&name)<'db> {
                $field_indices
                pub fn new_green(db: &'db dyn Database, $params) -> $(&green_name)<'db> {
                    let children = [$args];
                    let width =
                        children.into_iter().map(|id: GreenId<'_>| id.long(db).width(db)).sum();
                    $(&green_name)(GreenNode {
                        kind: SyntaxKind::$(&name),
                        details: GreenNodeDetails::Node { children: children.into(), width },
                    }.intern(db))
                }
            }
        }
    };
    quote! {
        #[derive(Clone, Debug, Eq, Hash, PartialEq, salsa::Update)]
        pub struct $(&name)<'db> {
            node: SyntaxNode<'db>,
        }
        $new_green_impl
        impl<'db> $(&name)<'db> {
            $body
        }
        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update, HeapSize)]
        pub struct $(&ptr_name)<'db>(pub SyntaxStablePtrId<'db>);
        impl<'db> $(&ptr_name)<'db> {
            $ptr_getters
        }
        impl<'db> TypedStablePtr<'db> for $(&ptr_name)<'db> {
            type SyntaxNode = $(&name)<'db>;
            fn untyped(self) -> SyntaxStablePtrId<'db> {
                self.0
            }
            fn lookup(&self, db: &'db dyn Database) -> $(&name)<'db> {
                $(&name)::from_syntax_node(db, self.0.lookup(db))
            }
        }
        impl<'db> From<$(&ptr_name)<'db>> for SyntaxStablePtrId<'db> {
            fn from(ptr: $(&ptr_name)<'db>) -> Self {
                ptr.untyped()
            }
        }
        #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, salsa::Update)]
        pub struct $(&green_name)<'db>(pub GreenId<'db>);
        impl<'db> TypedSyntaxNode<'db> for $(&name)<'db> {
            const OPTIONAL_KIND: Option<SyntaxKind> = Some(SyntaxKind::$(&name));
            type StablePtr = $(&ptr_name)<'db>;
            type Green = $(&green_name)<'db>;
            fn missing(db: &'db dyn Database) -> Self::Green {
                // Note: A missing syntax element should result in an internal green node
                // of width 0, with as much structure as possible.
                $(&green_name)(GreenNode {
                    kind: SyntaxKind::$(&name),
                    details: GreenNodeDetails::Node {
                        children: [$args_for_missing].into(),
                        width: TextWidth::default(),
                    },
                }.intern(db))
            }
            fn from_syntax_node(db: &'db dyn Database, node: SyntaxNode<'db>) -> Self {
                let kind = node.kind(db);
                assert_eq!(kind, SyntaxKind::$(&name), "Unexpected SyntaxKind {:?}. Expected {:?}.", kind, SyntaxKind::$(&name));
                Self { node }
            }
            fn cast(db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<Self> {
                let kind = node.kind(db);
                if kind == SyntaxKind::$(&name) {
                    Some(Self::from_syntax_node(db, node))
                } else {
                    None
                }
            }
            fn as_syntax_node(&self) -> SyntaxNode<'db> {
                self.node
            }
            fn stable_ptr(&self, db: &'db dyn Database) -> Self::StablePtr {
                $(&ptr_name)(self.node.stable_ptr(db))
            }
        }
    }
}
